﻿using System;
using Microsoft.Xna.Framework;

namespace ThreeDWorld
{
    public class Camera
    {
        public Vector3 Position;
        public Vector3 Target;
        public Vector3 Up;
        public float FieldOfViewRadians;
        public float AspectRatio;
        public float NearPlane;
        public float FarPlane;

        public Camera()
        {
            Position = Vector3.Zero;
            Target = Vector3.UnitZ;
            Up = Vector3.UnitY;
            FieldOfViewRadians = MathHelper.Pi / 3;
            AspectRatio = 800.0f / 600.0f;
            NearPlane = 1;
            FarPlane = 5000;

            CalcTransform();
        }

        public Matrix View
        {
            get;
            private set;
        }

        public Matrix Projection
        {
            get;
            private set;
        }

        public BoundingFrustum Frustum
        {
            get;
            private set;
        }

        public void CalcTransform()
        {
            View = Matrix.CreateLookAt(Position, Target, Vector3.Normalize(Up));
            Projection = Matrix.CreatePerspectiveFieldOfView(FieldOfViewRadians, AspectRatio, NearPlane, FarPlane);
            Frustum = new BoundingFrustum(View * Projection);
        }

        public void LookAt(Vector3 target)
        {
            Vector3 d = target - Position;
            Vector3 f = Vector3.Normalize(d);
            Vector3 u = Vector3.UnitY;
            Vector3 r;
            float dot = Vector3.Dot(f, u);

            if (dot < -0.9999 || dot > 0.9999)
            {
                u = Vector3.UnitX;
            }

            r = Vector3.Normalize(Vector3.Cross(u, f));
            u = Vector3.Normalize(Vector3.Cross(f, r));

            Target = Position + f * d.Length();
            Up = u;

            CalcTransform();
        }

        public void MoveTo(Vector3 position)
        {
            Vector3 f = Target - Position;

            Position = position;
            Target = position + f;

            CalcTransform();
        }

        public void Rotate(float rightAxisRadians, float upAxisRadians)
        {
            Vector3 d = Position - Target;
            Vector3 f = Vector3.Normalize(d);
            Vector3 u = Vector3.Normalize(Up);
            Vector3 r = Vector3.Normalize(Vector3.Cross(f, u));
            Matrix rot = Matrix.CreateRotationY(upAxisRadians);

            f = Vector3.TransformNormal(f, rot);
            r = Vector3.TransformNormal(r, rot);
            u = Vector3.Normalize(Vector3.Cross(r, f));

            rot = Matrix.CreateFromAxisAngle(r, rightAxisRadians);
            f = Vector3.TransformNormal(f, rot);
            u = Vector3.TransformNormal(u, rot);

            Position = Target + f * d.Length();
            Up = u;

            CalcTransform();
        }
    }
}
